/*
 * Copyright (c) 2011 Jean-Marc Perronne
 * Ensisa, 12 rue des Freres Lumieres, F-68093 Mulhouse cedex
 * All rights reserved.
 *
 * The copyright of this software is owned by Werner Randelshofer. 
 * You may not use, copy or modify this software, except in  
 * accordance with the license agreement you entered into with  
 * Jean-Marc Perronne. For details see accompanying license terms. 
 */

package fr.uha.ensisa.jmp.drone.handler;

import java.io.IOException;
import java.io.PrintStream;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.Timer;
import java.util.TimerTask;

import fr.uha.ensisa.jmp.drone.app.DroneConstants;
import fr.uha.ensisa.jmp.drone.at_commands.AtCommand;
import fr.uha.ensisa.jmp.drone.com.ATCommandConnection;
import fr.uha.ensisa.jmp.drone.com.BadInterfaceException;
import fr.uha.ensisa.jmp.drone.input.progressive.FlightInputs;


public class AtCommandHandler extends ChildDroneHandler<String>
{
	public enum SendingMode {
		SEND_ONLY, SEND_AND_DUMP, DUMP_ONLY
	};

	private ATCommandConnection com;
	private FlightInputs flihgtsInputs;
	
	private volatile int sequenceNumber;
	private StringBuilder commandsBuilder;

	private SendingMode sendingMode;
	private PrintStream dumpStream;

	private Timer timer;

	public AtCommandHandler(DroneHandler drone) throws SocketException, UnknownHostException, BadInterfaceException
	{
		this(drone, null);
	}

	public AtCommandHandler(DroneHandler drone, FlightInputs fi) throws SocketException, UnknownHostException, BadInterfaceException
	{
		super(drone);
		this.flihgtsInputs = fi;
		this.com = new ATCommandConnection();
		this.resetSequenceNumber();
		this.commandsBuilder = new StringBuilder();
		this.sendingMode = SendingMode.SEND_ONLY;
	}
	
	void flihgtsInputs(FlightInputs fi)
	{
		this.flihgtsInputs = fi;
	}

	public synchronized void resetSequenceNumber()
	{
		this.sequenceNumber = 1;
	}

	public void sendOnlyMode()
	{
		this.sendingMode = SendingMode.SEND_ONLY;
	}

	public void sendAndDumpMode(PrintStream stream)
	{
		this.sendingMode = SendingMode.SEND_AND_DUMP;
		this.dumpStream = stream;
	}

	public void sendDumpOnlyMode(PrintStream stream)
	{
		this.sendingMode = SendingMode.DUMP_ONLY;
		this.dumpStream = stream;
	}
	
	private String finalizeCommand(AtCommand cde)
	{
		cde.sequenceNbr(sequenceNumber++);
		return cde.toString();
	}

	private String ConcatCommands()
	{
		this.commandsBuilder.delete(0, this.commandsBuilder.length());
		while (true)
		{
			String c = this.data().poll();
			if (c == null) break;
			this.commandsBuilder.append(c);
		}
		return this.commandsBuilder.toString();
	}

	private void sendInputCommand(AtCommand c) throws IOException
	{
		byte[] bytes = this.finalizeCommand(c).getBytes();
		boolean s = false, d = false;
		switch (this.sendingMode)
		{
			case SEND_AND_DUMP :
				s = true;
				d = true;
				break;
			case SEND_ONLY :
				s = true;
				d = false;
				break;
			case DUMP_ONLY :
				s = false;
				d = true;
				break;
		}

		if (s)
		{
			//System.err.println(new String(bytes));
			this.com.send(bytes);
		}
		if (d && this.dumpStream != null)
		{
			this.dumpStream.println("at: " + String.valueOf(System.currentTimeMillis()) + " ms " + new String(bytes));
		}
	}

	public synchronized void sendImmediately(AtCommand cde) throws IOException
	{
		this.com.send(this.finalizeCommand(cde).getBytes());
	}

	public synchronized void sendScheduled(AtCommand cde)
	{
		this.data().add(this.finalizeCommand(cde));
	}

	@Override
	public void run()
	{
		try
		{
			if (!this.isActive()) return;
			if (!this.drone.isActive()) this.terminate();
			
			if (this.flihgtsInputs != null)
			{
				AtCommand c = this.flihgtsInputs.command();
				if (c != null) this.sendInputCommand(c);
				if (this.flihgtsInputs.failed()) 
				{		
					System.err.println("flight inputs failed");
					this.terminate();
				}
			}

			byte[] cde = this.ConcatCommands().getBytes();
			//System.out.println(new String(cde));
			if (cde.length != 0) this.com.send(cde);
		}
		catch (IOException e)
		{
			e.printStackTrace();
			this.terminate();
		}		
	}

	private void timerTaskRun()
	{
		this.run();
	}
	
	public void start()
	{
		super.start();
		this.timer = new Timer();
		this.timer.schedule(new TimerTask()
		{
			@Override
			public void run()
			{
				timerTaskRun();
			}
			
		}, 0, DroneConstants.AT_REFRESH_MS);
		this.running();
	}
	
	public synchronized void terminate()
	{
		super.terminate();
		this.timer.cancel();
		this.notRrunning();
	}

	@Override
	public void close()
	{
		this.com.close();
	}

}
